package main

import (
	"encoding/xml"
	"flag"
	"fmt"
	"io"
	"os"
	"strings"
)

type AttributeValue struct {
	Attribute string
	Value     string
}

func (av AttributeValue) String() string {
	return fmt.Sprintf("%s=%s", av.Attribute, av.Value)
}

type AttributeFlag struct {
	Attributes []*AttributeValue
}

func (af *AttributeFlag) Set(a string) error {
	if len(a) == 0 {
		return nil
	}
	pairs := strings.Split(a, " ")
	for _, pair := range pairs {
		var av AttributeValue
		sp := strings.Split(pair, "=")
		if len(sp) == 2 {
			av.Attribute = sp[0]
			av.Value = sp[1]
		}
		af.Attributes = append(af.Attributes, &av)
	}
	return nil
}

func (af *AttributeFlag) String() string {
	var as []string
	for _, v := range af.Attributes {
		as = append(as, fmt.Sprintf("%s", v))
	}
	return strings.Join(as, " ")
}

func main() {

	// Exercise 7.17
	af := AttributeFlag{}
	flag.CommandLine.Var(
		&af,
		"filter_attr",
		"eg. filter attributes 'attr=value attr1=value1'")
	flag.Parse()

	dec := xml.NewDecoder(os.Stdin)
	var stack []string
	var elements []xml.StartElement
	for {
		tok, err := dec.Token()
		if err == io.EOF {
			break
		} else if err != nil {
			fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err)
			os.Exit(1)
		}

		switch tok := tok.(type) {
		case xml.StartElement:
			stack = append(stack, tok.Name.Local)
			elements = append(elements, tok)
		case xml.EndElement:
			stack = stack[:len(stack)-1]
			elements = elements[:len(elements)-1]
		case xml.CharData:
			if containsAll(stack, flag.Args()) &&
				filterAttrs(&af, elements[len(elements)-1]) {
				fmt.Printf(
					"%s: %s\n",
					strings.Join(stack, " "),
					tok)
			}
		}
	}
}

func filterAttrs(af *AttributeFlag, e xml.StartElement) bool {
	if len(af.Attributes) == 0 {
		return true
	}

	for _, av := range af.Attributes {
		found := false

		for _, attr := range e.Attr {
			if av.Attribute == attr.Name.Local {
				if av.Value != attr.Value {
					return false
				}
				found = true
			}
		}
		if !found {
			return false
		}
	}

	return true
}

func containsAll(a, b []string) bool {
	for len(a) >= len(b) {
		if len(b) == 0 {
			return true
		}
		if a[0] == b[0] {
			a, b = a[1:], b[1:]
		} else {
			a = a[1:]
		}
	}
	return false
}
